#include "serverServer.h"
#include "serverClientHandler.h"

Server::Server(unsigned int puerto):socket(puerto) {
	this->cache = new Cache();
	this->conexiones = new std::list<ClientHandler*>;
}

bool Server::reemplazarClienteInactivo(int fdClienteNuevo) {
	std::list <ClientHandler*>::iterator it;
	it = conexiones->begin();
	bool actividad = false;

	while ((it != conexiones->end())) {
		actividad = (*it)->estaActivo();
		if (!(actividad)) {
			// Libero recursos cliente viejo.
			shutdown((*it)->getFdCliente(), SHUT_RDWR);
			close((*it)->getFdCliente());
			// Seteo nuevo file descriptor al client handler.
			(*it)->setFdCliente(fdClienteNuevo);
			(*it)->setActividad(true);

			return true;
		}
		it++;
	}
	return  false;
}

int Server::leer(ClientHandler* cliente, void *buf, size_t tamanio) {
	int bytesLeidos = 0;
	unsigned int bytesAcumulados = 0;
	while ((bytesAcumulados < tamanio)
			&& (estaCorriendo())
			&& cliente->estaActivo()) {
		bytesLeidos = recv(cliente->getFdCliente(), buf, tamanio, 0);
		if (bytesLeidos > 0)  // No hubo error al leer.
			bytesAcumulados += bytesLeidos;
		if (bytesLeidos == 0)
			cliente->setActividad(false);
	}
	return bytesAcumulados;
}

int Server::escribir(ClientHandler* cliente , void *buf, size_t tamanio) {
	int bytesEsritos = 0;
	unsigned int bytesAcumulados = 0;
	while ((bytesAcumulados < tamanio)
			&& (estaCorriendo())
			&& cliente->estaActivo()) {
		bytesEsritos = send(cliente->getFdCliente(), buf, tamanio, 0);
		if (bytesEsritos > 0)  // No hubo error al escribir.
			bytesAcumulados += bytesEsritos;
		if (bytesEsritos == 0)
			cliente->setActividad(false);
	}
	return bytesAcumulados;
}

void Server::ejecutar() {
	int fdCliente = 0;

	while (estaCorriendo()) {
		fdCliente = socket.aceptar();
		ClientHandler *clientHandler;
		bool hayClienteInactivo = false;

		if (fdCliente > 0) {
			hayClienteInactivo = reemplazarClienteInactivo(fdCliente);

			if (!hayClienteInactivo) {
				clientHandler = new ClientHandler(this, fdCliente);
				conexiones->push_back(clientHandler);
				clientHandler->run();
			}
		}
	}
}

Cache* Server::getCache() const {
    return cache;
}

void Server::setCache(Cache *cache) {
    this->cache = cache;
}

tDato* Server::obtenerClaveValor(const ModeloProtocolo& modelo) {
	char *clave = NULL, *valor = NULL, *ptrAux = NULL;
	char *argumento = modelo.getArgumento();
	char caracter;
	int tamanioClave = 0, tamanioValor = 0;
	tDato *dato;

	if (argumento)
		caracter = argumento[0];

	while ((caracter!= SEPARADOR_CLAVE_VALOR) &&
			((unsigned int)tamanioClave) <  modelo.getLongitudArgumento()) {
		tamanioClave++;
		caracter = argumento[tamanioClave];
	}

	clave = new char[tamanioClave + LONGITUD_CARACTER_FIN_STRING];
	memcpy(clave, argumento, tamanioClave);
	clave[tamanioClave] = CARACTER_FIN_STRING;

	tamanioValor = (modelo.getLongitudArgumento() - 
				    tamanioClave - 
				    LONGITUD_SEPARADOR);
	ptrAux = argumento + tamanioClave + LONGITUD_SEPARADOR;
	if (tamanioValor >= 0) {
		valor = new char[tamanioValor + LONGITUD_CARACTER_FIN_STRING];
		memcpy(valor, ptrAux, tamanioValor);
		valor[tamanioValor] = CARACTER_FIN_STRING;
	}
	else {
		valor = new char[0];
		memcpy(valor, ptrAux, 0);
	}

	dato = new tDato(clave, valor);


	return dato;
}

std::string Server::procesarOperacion(const ModeloProtocolo& modelo) {
	std::string operacion, accion, exito, resultado;
	tDato dato = *(obtenerClaveValor(modelo));
	bool exitoOperacion = false, operacionValida = false;

	if (modelo.getCodigo() == CODIGO_INSERTAR) {
		operacionValida = true;
		operacion = IMPRESION_INSERTAR;
		accion = IMPRESION_INSERTAR;
		exitoOperacion = cache->insertar(dato);
	}

	else if (modelo.getCodigo() == CODIGO_ELIMINAR) {
		operacionValida = true;
		operacion = IMPRESION_ELIMINAR;
		// si esta no fuera distinta podria usar el mismo label de impresion
		// y no necesitaria de la variable accion.
		accion = RESULTADO_BORRAR;
		exitoOperacion = cache->eliminar(dato.first);
	}
	else if (modelo.getCodigo() == CODIGO_CONSULTAR) {
		operacionValida = true;
		operacion = IMPRESION_CONSULTAR;
		accion = IMPRESION_CONSULTAR;
		resultado = cache->consultar(dato.first);
		if (resultado.size() > 0)
			exitoOperacion = true;
	}

	exito = (exitoOperacion)? IMPRESION_EXITO : IMPRESION_FALLO;

	if (operacionValida) {
		std::cout << "Operacion recibida: " << operacion << std::endl;
		if ((modelo.getCodigo() == CODIGO_CONSULTAR) && (exitoOperacion))
			// Es consulta exitosa.
			std::cout << accion
					  << " "
					  << exito
					  << ". Clave: "
					  << dato.first
					  << ". Valor: "
					  << resultado
					  << std::endl;
		else
			std::cout << accion
					  << " "
					  << exito
					  << ". Clave: "
					  << dato.first
					  <<  std::endl;
	}
	else if (estaCorriendo())
		std::cerr << SALIDA_OPERACION_INVALIDA << std::endl;

	return resultado;
}

ModeloRespuesta* Server::obtenerPedido(ClientHandler* cliente) {
	char *bufferHeader, *bufferArgumento, *ptr;
	ModeloProtocolo modelo;
	ModeloRespuesta *modeloRespuesta = new ModeloRespuesta();;
	unsigned int longitudArgumento = 0;
	std::string resultado;

	bufferHeader = new char[LONGITUD_DATOS_INICIALES];
	// leo encabezado.
	leer(cliente, bufferHeader, LONGITUD_DATOS_INICIALES);

	ptr = bufferHeader;
	ptr += sizeof(char);
	longitudArgumento = *(unsigned int*)(ptr);

	if (estaCorriendo() && cliente->estaActivo()) {
	 	bufferArgumento = new char[longitudArgumento];
		// leo bytes restantes.
	 	leer(cliente, bufferArgumento, longitudArgumento);

		modelo.hidratar(bufferHeader, bufferArgumento);
		resultado = procesarOperacion(modelo);

		modeloRespuesta->setRespuesta(resultado);

		if (modelo.getCodigo() == CODIGO_CONSULTAR)
			modeloRespuesta->setCodigo(CODIGO_RESPUESTA_CON_DATO);

		delete[] bufferHeader;
		delete[] bufferArgumento;
	}

	return modeloRespuesta;
}

std::list<ClientHandler*>* Server::getConexiones() const {
    return conexiones;
}

void Server::responderPedido(ClientHandler* cliente,
							 ModeloRespuesta respuestaPedido) {
	char *datos = respuestaPedido.serializar();

	int tamanioDatos = TAMANIO_HEADER_RESPUESTA +
					   respuestaPedido.getRespuesta().size();

	if (estaCorriendo() && cliente->estaActivo()) {
	escribir(cliente, (void*)datos, tamanioDatos);
	}
}

ServerSocket Server::getSocket() const {
    return socket;
}

void Server::cerrar() {
	socket.cerrar();
	stop();
}

Server::~Server() {
	std::list <ClientHandler*>::iterator it;
	it = conexiones->begin();
	int cliente;


	while ((it != conexiones->end())) {
		  cliente = (*it)->getFdCliente();

		  (*it)->stop();
		  it++;
	}
	socket.cerrar();
	delete conexiones;
	delete cache;
}
